﻿/*
 * Copyright (C) 2012, Jesus Requejo <jesus.requejo@gmail.com> 
 * Licensed under Microsoft Public License (Ms-PL)
 * http://www.microsoft.com/en-us/openness/licenses.aspx#MPL
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Infrastructure;
using LoaderServiceManager.AppLogic;

namespace LoaderServiceManager {
   public partial class EditPermissions : Form {

      protected BindingList<WindowsUser> AlreadyAuthorized { get; set; }
      protected IList<RadioButton> OrderedRadioButtons { get; set; }
      protected IList<string> RadioTags { get; set; }
      protected string CurrentUserClass { get; set; }
      protected RadioButton InitialRadioButton { get; set; }
      protected ApplicationServices ApplicationServices { get; set; }
      private UsersListDialog UsersDialog { get; set; }


      public bool IsDirty { get; set; }


      protected EditPermissions() {
         InitializeComponent();
      }

      public EditPermissions(
         string groupBoxText,
         string labelText,
         string radioButtonText,
         BindingList<WindowsUser> usersList,
         IList<string> radioTags,
         string currentUserClass,
         ApplicationServices appServices
         )
         : this() {
         permissionsGroupBox.Text = groupBoxText;
         permissionsLabel.Text = labelText;
         everyOrNoOneRadioButton.Text = radioButtonText;
         AlreadyAuthorized = usersList;
         AlreadyAuthorized.AllowNew = true;
         AlreadyAuthorized.AllowRemove = true;
         RadioTags = radioTags;
         CurrentUserClass = currentUserClass;
         IsDirty = false;
         ApplicationServices = appServices;
         UsersDialog = new UsersListDialog(this);

         OrderedRadioButtons = new List<RadioButton>() {
            everyOrNoOneRadioButton,
            adminsRadioButton,
            authorizedRadioButton
         };
         InitializeDialogValues();
         SetupEvents();
      }

      public string GetSelectedCheckBoxTag() {
         var selected = OrderedRadioButtons.Where(button => button.Checked == true).First();
         return (string)selected.Tag;
      }

      public IEnumerable<string> GetUsersList() {
         var usersList = usersListBox.Items.Cast<WindowsUser>().Select(u => u.Name);
         return usersList;
      }

      private void SetupEvents() {
         addUserLink.Click += UsersDialog.addUserLink_Click;
         removeUserLink.Click += UsersDialog.removeUserLink_Click;
         everyOrNoOneRadioButton.Click += new EventHandler(RadioButton_Click);
         adminsRadioButton.Click += new EventHandler(RadioButton_Click);
         authorizedRadioButton.Click += new EventHandler(RadioButton_Click);
         okButton.Click += new EventHandler(okButton_Click);
         cancelButton.Click += new EventHandler(cancelButton_Click);
      }

      void okButton_Click(object sender, EventArgs e) {
         DialogResult = System.Windows.Forms.DialogResult.OK;
         Close();
      }

      void cancelButton_Click(object sender, EventArgs e) {
         if (IsDirty) {
            var result = MessageBox.Show("You have unsaved changes. Are you sure you want to discard them?",
               "Confirm",
               MessageBoxButtons.YesNo,
               MessageBoxIcon.Warning
               );
            if (result == System.Windows.Forms.DialogResult.Yes) {
               DialogResult = System.Windows.Forms.DialogResult.Cancel;
               Close();
            }
         }
         else {
            DialogResult = System.Windows.Forms.DialogResult.Cancel;
            Close();
         }
      }

      void RadioButton_Click(object sender, EventArgs e) {
         var clickedButton = sender as RadioButton;
         UpdateDirtyState(clickedButton);
         UpdateListControlEnabledProperty();
      }

      private void UpdateDirtyState(RadioButton clickedButton) {
         if (!IsDirty && clickedButton != InitialRadioButton) {
            IsDirty = true;
         }
      }

      private void InitializeDialogValues() {
         for (int i = 0; i < OrderedRadioButtons.Count; i++) {
            OrderedRadioButtons[i].Tag = RadioTags[i];
         }
         var button = CheckRadioButtonThatMatchesCurrentUserClass();
         InitialRadioButton = button;
         BindUsersList();
         UpdateListControlEnabledProperty();
         UpdateRemoveLink();
      }

      private RadioButton CheckRadioButtonThatMatchesCurrentUserClass() {
         var button = OrderedRadioButtons.Where(b => (string)b.Tag == CurrentUserClass).First();
         button.Checked = true;
         return button;
      }

      private void BindUsersList() {
         this.usersListBox.DataSource = AlreadyAuthorized;
         this.usersListBox.DisplayMember = StronglyTypedProperty<WindowsUser>.Map(u => u.Name);
      }

      private void UpdateListControlEnabledProperty() {
         if (authorizedRadioButton.Checked) {
            usersListBox.Enabled = true;
            addUserLink.Enabled = true;
            UpdateRemoveLink();
         }
         else {
            usersListBox.Enabled = false;
            addUserLink.Enabled = false;
            removeUserLink.Enabled = false;
         }
      }


      private void UpdateDirtyState(IEnumerable<string> usersToAddOrRemove) {
         if (usersToAddOrRemove.Count() > 0) {
            IsDirty = true;
         }
      }

      private void AddUsersToList(IEnumerable<string> usersToAdd) {
         foreach (var userName in usersToAdd) {
            AlreadyAuthorized.Add(new WindowsUser(userName, "User"));
         }
         usersListBox.DataBindings.Clear();
         AlreadyAuthorized = new BindingList<WindowsUser>(AlreadyAuthorized.OrderBy(u => u.Name).ToList());
         usersListBox.DataSource = AlreadyAuthorized;
      }


      internal void RemoveUsersFromList(IEnumerable<string> usersToRemove) {
         var newList = AlreadyAuthorized.Select(wu => wu.Name).Except(usersToRemove).OrderBy(s => s).ToList();
         var newBindingList = newList.Select(name => new WindowsUser(name, "User")).ToList();
         usersListBox.DataBindings.Clear();
         AlreadyAuthorized = new BindingList<WindowsUser>(newBindingList);
         usersListBox.DataSource = AlreadyAuthorized;
      }

      private void UpdateRemoveLink() {
         if (usersListBox.Items.Count == 0) {
            removeUserLink.Enabled = false;
         }
         else {
            removeUserLink.Enabled = true;
         }
      }

      private class UsersListDialog {

         private EditPermissions Form { get; set; }
         private ApplicationServices AppServices { get; set; }
         private SynchronizationContext Context { get; set; }

         public UsersListDialog(EditPermissions form) {
            Form = form;
            AppServices = Form.ApplicationServices;
            Context = SynchronizationContext.Current;
         }

         // TODO: refactor addUserLink_Click, removeUserLink_Click -> code duplication -> extract common helper method

         public void addUserLink_Click(object sender, EventArgs e) {
            IList<string> availableUsers = new List<string>() { };
            PleaseWaitDialog waitDialog = new PleaseWaitDialog();

            System.Threading.ThreadPool.QueueUserWorkItem((o) => {
               availableUsers = GetAvailableUsers(Form.AlreadyAuthorized).ToList();
               Context.Post((state) => {
                  var f = state as PleaseWaitDialog;
                  if (f != null) {
                     f.Close();
                  }
               }, waitDialog);
            });
            Application.DoEvents();
            waitDialog.BringToFront();
            waitDialog.ShowWaitDialog("Please wait while retrieving Windows users...");

            var administratorIndexes = FindOutAdministratorsIndexes(availableUsers);
            var decoratedAvailableUsers = MarkAdministrators(availableUsers, administratorIndexes);
            bool someoneIsAdmin = administratorIndexes.Count > 0 ? true : false;
            string title = "Add users";
            string label = "Select users to add to the authorization list";
            UsersCheckList addUsers = new UsersCheckList(decoratedAvailableUsers, someoneIsAdmin, title, label);
            addUsers.ShowDialog();
            var selectedUsers = addUsers.SelectedUsers;
            if (selectedUsers.Count() != 0) {
               var usersToAdd = UnmarkAdministrators(selectedUsers);
               Form.UpdateDirtyState(usersToAdd);
               Form.AddUsersToList(usersToAdd);
               Form.UpdateRemoveLink();
            }
         }

         public void removeUserLink_Click(object sender, EventArgs e) {
            var candidates = Form.AlreadyAuthorized.Select(u => u.Name).ToList();
            var administratorIndexes = FindOutAdministratorsIndexes(candidates);
            var decoratedAvailableUsers = MarkAdministrators(candidates, administratorIndexes);
            bool someoneIsAdmin = administratorIndexes.Count > 0 ? true : false;
            string title = "Remove users";
            string label = "Select users to be removed from the authorization list";
            UsersCheckList usersDialog = new UsersCheckList(decoratedAvailableUsers, someoneIsAdmin, title, label);
            usersDialog.ShowDialog();
            var selectedUsers = usersDialog.SelectedUsers;
            if (selectedUsers.Count() != 0) {
               var usersToRemove = UnmarkAdministrators(selectedUsers);
               Form.UpdateDirtyState(usersToRemove);
               Form.RemoveUsersFromList(usersToRemove);
               Form.UpdateRemoveLink();
            }
         }

         private IEnumerable<string> GetAvailableUsers(BindingList<WindowsUser> alreadyAuthorized) {
            var allUsers = AppServices.GetWindowsUsers().
               Select(u => WindowsUsersUtils.ConvertHostNameToUpperCase(u.Name));
            var alreadyAdded = alreadyAuthorized.
               Select(u => WindowsUsersUtils.ConvertHostNameToUpperCase(u.Name));
            var notYetAdded = allUsers.
               Except(alreadyAdded).
               OrderBy(name => name);

            return notYetAdded;
         }

         private IList<int> FindOutAdministratorsIndexes(IList<string> users) {
            var allAdmins = WindowsUsersUtils.GetAllAdminUsers().OrderBy(u => u.Name);
            var sortedUsers = users.OrderBy(s => s);
            var indexes = sortedUsers.Select((u, i) => new { u, i })
               .Where(au => allAdmins.Select(a => a.Name).Contains(au.u))
               .Select(au => au.i);

            return indexes.ToList();
         }

         private IList<string> MarkAdministrators(IList<string> users, IList<int> administratorIndexes) {
            var sortedUsers = users.OrderBy(s => s).ToList();
            var resultList = new List<string>(sortedUsers);
            for (int i = 0; i < administratorIndexes.Count; i++) {
               var currentAdminIndex = administratorIndexes[i];
               var name = (string)sortedUsers[currentAdminIndex];
               var newName = name + "*";
               resultList[currentAdminIndex] = newName;
            }
            return resultList;
         }

         private IEnumerable<string> UnmarkAdministrators(IList<string> users) {
            var sortedUsers = users.OrderBy(s => s);
            var correctedNames = sortedUsers.Select(name => {
               string correctedName = name;
               if (name.IndexOf('*') >= 0) {
                  correctedName = name.Substring(0, name.Length - 1);
               }
               return correctedName;
            });
            return correctedNames;
         }
      }


   }
}
